package com.nononsenseapps.feeder.worker

import android.content.Context
import android.os.Build
import androidx.work.*
import com.nononsenseapps.feeder.db.ARG
import com.nononsenseapps.feeder.db.room.ID_UNSET
import com.nononsenseapps.feeder.parser.FeedParser
import com.nononsenseapps.feeder.sync.local.ResponseFailure
import com.nononsenseapps.feeder.sync.local.syncFeeds
import com.nononsenseapps.feeder.util.*
import com.nononsenseapps.jsonfeed.Feed
import com.nononsenseapps.jsonfeed.NOSET_CONNECT_TIMEOUT_SECS
import okhttp3.Response
import timber.log.Timber
import java.net.URL
import java.util.concurrent.TimeUnit

const val ARG_FORCE_NETWORK = "force_network"
const val ARG_FORCE = "force"
const val UNIQUE_PERIODIC_NAME = "feeder_periodic"
const val PARALLEL_SYNC = "parallel_sync"
const val MIN_FEED_AGE_MINUTES = "min_feed_age_minutes"
const val IGNORE_CONNECTIVITY_SETTINGS = "ignore_connectivity_settings"
const val FEED_ADDED_BROADCAST = "feeder.nononsenseapps.RSS_FEED_ADDED_BROADCAST"

const val SYNC_BROADCAST = "feeder.nononsenseapps.RSS_SYNC_BROADCAST"
const val SYNC_BROADCAST_IS_ACTIVE = "IS_ACTIVE"

fun isOkToSyncAutomatically(context: Context): Boolean =
        (currentlyConnected(context)
                && (!PrefUtils.shouldSyncOnlyWhenCharging(context) || currentlyCharging(context))
                && (!PrefUtils.shouldSyncOnlyOnWIfi(context) || currentlyUnmetered(context))
                )

class FeedSyncer(val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
    override suspend fun doWork(): Result {
        val goParallel = inputData.getBoolean(PARALLEL_SYNC, false)
        val ignoreConnectivitySettings = inputData.getBoolean(IGNORE_CONNECTIVITY_SETTINGS, false)

        var success = false

        if (ignoreConnectivitySettings || isOkToSyncAutomatically(context)) {

            val feedId = inputData.getLong(ARG.FEED_ID, ID_UNSET)
            val feedTag = inputData.getString(ARG.TAG) ?: ""
            val forceNetwork = inputData.getBoolean(ARG_FORCE_NETWORK, false)
            val minFeedAgeMinutes = inputData.getInt(MIN_FEED_AGE_MINUTES, 15)
            val force = inputData.getBoolean(ARG_FORCE, false)

            val syncState= syncFeeds(
                    context = applicationContext,
                    feedId = feedId,
                    feedTag = feedTag,
                    forceNetwork = forceNetwork,
                    force = force,
                    parallel = goParallel,
                    minFeedAgeMinutes = minFeedAgeMinutes
            )

            EventBusUtils.fireSyncStateChange(syncState)

            success = syncState.success

            // Send notifications for configured feeds
            //notify(applicationContext)
            Timber.d("fire notify state checked notify:${syncState.notify}\tnewCount:${syncState.count}")

            if(syncState.success&&syncState.notify&&syncState.count>0){
                Timber.d("fire notify feed item added event")
                EventBusUtils.fireNotifyStateChange(syncState)
            }
        }

        return when (success) {
            true -> Result.success()
            false -> Result.failure()
        }
    }
}

fun requestFeedSync(context: Context, feedId: Long = ID_UNSET,
                    feedTag: String = "",
                    ignoreConnectivitySettings: Boolean = false,
                    forceNetwork: Boolean = false,
                    force : Boolean = false,
                    parallell: Boolean = false) {
    val workRequest = OneTimeWorkRequestBuilder<FeedSyncer>()

    val data = workDataOf(ARG.FEED_ID to feedId,
            ARG.TAG to feedTag,
            PARALLEL_SYNC to parallell,
            ARG_FORCE to force,
            IGNORE_CONNECTIVITY_SETTINGS to ignoreConnectivitySettings,
            ARG_FORCE_NETWORK to forceNetwork)

    workRequest.setInputData(data)

    if(feedId == ID_UNSET &&feedTag.isEmpty())
         workRequest.setInitialDelay(10,TimeUnit.SECONDS)

    WorkManager.getInstance(context).enqueueUniqueWork(UNIQUE_PERIODIC_NAME+feedId,ExistingWorkPolicy.KEEP,workRequest.build())
}

fun configurePeriodicSync(context: Context, forceReplace: Boolean = false) {
    val shouldSync = PrefUtils.shouldSync(context)

    if (shouldSync) {
        val constraints = Constraints.Builder()
                .setRequiresBatteryNotLow(true)
                .setRequiresCharging(PrefUtils.shouldSyncOnlyWhenCharging(context))

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            constraints.setRequiresDeviceIdle(true)
        }

        if (PrefUtils.shouldSyncOnlyOnWIfi(context)) {
            constraints.setRequiredNetworkType(NetworkType.UNMETERED)
        } else {
            constraints.setRequiredNetworkType(NetworkType.CONNECTED)
        }

        var timeInterval = PrefUtils.synchronizationFrequency(context)

        if (timeInterval in 1..12 || timeInterval == 24L) {
            // Old value for periodic sync was in hours, convert it to minutes
            timeInterval *= 60
            PrefUtils.setSynchronizationFrequency(context, timeInterval)
        }

        val workRequestBuilder = PeriodicWorkRequestBuilder<FeedSyncer>(
                timeInterval, TimeUnit.MINUTES,
                timeInterval / 2, TimeUnit.MINUTES)

        val syncWork = workRequestBuilder
                .setConstraints(constraints.build())
                .addTag("periodic_sync")
                .build()

        val existingWorkPolicy = if (forceReplace) {
            ExistingPeriodicWorkPolicy.REPLACE
        } else {
            ExistingPeriodicWorkPolicy.KEEP
        }

        WorkManager.getInstance(context)
                .enqueueUniquePeriodicWork(UNIQUE_PERIODIC_NAME,
                        existingWorkPolicy,
                        syncWork)
    } else {
        WorkManager.getInstance(context)
                .cancelUniqueWork(UNIQUE_PERIODIC_NAME)
    }
}

suspend fun syncFeed(feedParser: FeedParser,
                     feedUrl: URL,
                     connectTimeoutSecs: Long = NOSET_CONNECT_TIMEOUT_SECS, forceNetwork: Boolean = false, error:(e:Throwable)->Unit={}): Feed? {
    try {
        val response: Response = feedParser.getResponse(feedUrl, forceNetwork = forceNetwork,connectTimeoutSecs = connectTimeoutSecs)

        return response.use {
            it.body?.use { responseBody ->
                val body = responseBody.bytes()
                when {
                    !response.isSuccessful -> {
                        throw ResponseFailure("${response.code} when fetching : $feedUrl")
                    }
                    else -> feedParser.parseObjectResponse(it, body)
                }
            }
        }
    } catch (e: ResponseFailure) {
        Timber.e( "Failed to fetch : ${e.message}")
        error(e)
    } catch (t: Throwable) {
        Timber.e(  "Something went wrong: ${t.message}")
        error(t)
    }
    return null
}
